#include "MoonGenerator.h"

std::string MoonGenerator::arithmeticOperators[TAC_OR-TAC_MULT+2] = {
		"mul", "div", "mod",
		"add", "sub", 
		"clt", "cgt", "ceq", "cle", "cge", "cne",
		"and", "or" 
};

MoonGenerator::MoonGenerator(std::string outputFile, TCodeInterface* iCodeRep)
{
	iCode = iCodeRep;
	paramCount = 0;
	initTargetCode( outputFile ); 
}

MoonGenerator::~MoonGenerator()
{
}

void MoonGenerator::initTargetCode(std::string filename) {
	std::string newFilename = filename + ".m";
	outputFile.open(newFilename.c_str(), std::ios::out | std::ios::trunc);
	if(!outputFile.is_open()) {
		perror("Error creating target code file");
	}
	//TODO add compiler info as a comment
}

void MoonGenerator::setCharacterConsts(std::string charConsts) {
	characterConstants = charConsts;
}

void MoonGenerator::setNumericalConsts(std::string numConsts) {
	numericalConstants = numConsts;
}

void MoonGenerator::generateTargetCode() {
	// dump the literals
	if(!numericalConstants.empty()) {
		outputFile << "nums\tdw\t" << numericalConstants << "\n";
	}
	
	if(characterConstants.length() > 2) {
		outputFile << "chars\tdb\t" << characterConstants << "\n";
	}
	
	outputFile << "\n\t" << "align" <<
			"\n\t" << "entry" <<
			"\n\t" << "addi " << REG_BASE << ",R0,LDATA" << 
			"\n\t" << "add " << REG_FSIZE << ",R0,R0" << "\n";
	
	
	while(iCode->hasNextTAC()) {
		convertIntermediateCode( iCode->nextTAC() );
	}
	outputFile << "LDATA";
	outputFile.close();
}

void MoonGenerator::convertIntermediateCode(const ThreeAddressCode* tac) {
	OpCode op = tac->getOpCode();
	
	if(tac->hasLabel()) {
		outputFile << tac->getLabel();
	}
	
	if(op == TAC_PARAM) {		
		if(++paramCount == 1) {
			outputFile << "\t" << "add " << REG_GENP_1 << "," << REG_BASE << "," << REG_FSIZE <<
			"\t% PARAM " << paramCount << "\n" <<
			"\t" << "addi " << REG_GENP_1 << "," << REG_GENP_1 << "," << CONST_BOOKEEPING_SZ << "\n"; 			
		} else {
			outputFile << "\t" << "addi " << REG_GENP_1 << "," << REG_GENP_1 << "," << CONST_WORD_SZ <<
			"\t% PARAM " << paramCount << "\n"; 
		}
		
		if(tac->isOperand1aParam()) {
			outputFile << "\t" << "lw " << REG_GENP_2 << "," << tac->getOperand1() << "(" << REG_BASE << ")\n";
		} else {
			outputFile << "\t" << "addi " << REG_GENP_2 << "," << REG_BASE << "," << tac->getOperand1() << "\n";
		}
		
		outputFile << "\t" << "sw " << "0(" << REG_GENP_1 << ")," << REG_GENP_2 << "\n";
		return;
	} else {
		paramCount = 0;
	}
	
	if(op == TAC_HALT) {
		outputFile << "\t" << "hlt" << "\n";
	}
	else if(op == TAC_CALL) {
		int functionSize = iCode->getFunctionSize( tac->getGotoLabel() );
		outputFile << "\tadd " << REG_GENP_1 << "," << REG_BASE << "," << REG_FSIZE << 
		"\t% CALL " << tac->getGotoLabel() << "\n" <<
		// TODO separate this into it's own instruction TAC_CALL_GET_RET_VAL
		"\t" << "addi " << REG_GENP_2 << "," << REG_BASE << "," << tac->getResult() << "\n" <<
		"\t" << "sw " << ADDR_RET_VAL << "(" << REG_GENP_1 << ")," << REG_GENP_2 << "\n" <<
		// ******************************************************************
		"\t" << "sw " << ADDR_OLD_FSIZE << "(" << REG_GENP_1 << ")," REG_FSIZE << "\n" <<
		"\t" << "sw " << ADDR_OLD_BASE << "(" << REG_GENP_1 << ")," << REG_BASE << "\n" <<		
		"\t" << "add " << REG_BASE <<"," << REG_BASE << "," << REG_FSIZE << "\n" <<
		"\t" << "addi " << REG_FSIZE << ",R0," << functionSize << "\n" <<
		"\t" << "jl " << REG_RET_ADDR << "," << tac->getGotoLabel() << "\n";
	}
	else if(op == TAC_SAVE_RET_VAL) {
		outputFile << "\t" << "lw " << REG_GENP_1 << "," << ADDR_RET_VAL << "(" << REG_BASE << ")" <<
		"\t% Save return value\n";
		loadOperand1( tac, REG_GENP_2 );
		outputFile << "\t" << "sw " << "0(" << REG_GENP_1 << ")," << REG_GENP_2 << "\n";
	}
	else if(isArithmeticOperator(op)) {
		loadOperand1( tac, REG_GENP_1 );
		loadOperand2( tac, REG_GENP_2 );
		outputFile << "\t" << 
			getMoonArithmeticOperator(op) <<" " << REG_GENP_3 << "," << REG_GENP_1 << "," << REG_GENP_2 << "\n";
		saveResult( tac, REG_GENP_3 );
	} 
	else if(op == TAC_NOT) {
		loadOperand1( tac, REG_GENP_1 );
		outputFile << "\t" << "not " << REG_GENP_2 << "," << REG_GENP_1 << "\n";
		saveResult( tac, REG_GENP_2 );
	}
	else if(op == TAC_ADDRESS_OF) {
		loadOperand2( tac, REG_GENP_2 );
		outputFile << "\t" << "sl " << REG_GENP_2 << ",2\n";

		if(tac->isOperand1aParam()) {			
			outputFile << "\t" << "lw " REG_GENP_1 << "," << tac->getOperand1() << "(" << REG_BASE << ")\n" <<
			"\t" << "add " << REG_GENP_2 << "," << REG_GENP_2 << "," << REG_GENP_1 << "\n" <<
			"\t" << "lw " << REG_GENP_1 << ",0(" << REG_GENP_2 << ")\n";
		} else {		
			outputFile << "\t" << "add " << REG_GENP_2 << "," << REG_GENP_2 << "," << REG_BASE << "\n" <<
			"\t" << "lw " << REG_GENP_1 << "," << tac->getOperand1() << "(" << REG_GENP_2 << ")\n";
		}
		saveResult( tac, REG_GENP_1 );
	}
	else if(op == TAC_ASSIGN_TO_ADDRESS_AT) {
		loadOperand1( tac, REG_GENP_1 ); // the value to be assigned is in here
		loadOperand2( tac, REG_GENP_2 ); // the index is here

		outputFile << "\t" << "sl " << REG_GENP_2 << ",2\n"; // index * 4

		if(tac->isResultParam()) { // load the array's absolute addrress into R3
			outputFile << "\t" << "lw " << REG_GENP_3 << "," << tac->getResult() << "(" << REG_BASE << ")\n" <<
			"\t" << "add " << REG_GENP_3 << "," << REG_GENP_3 << "," << REG_GENP_2 << "\n";
		} else { // add the array address and the index
			outputFile << "\t" << "addi " << REG_GENP_3 << "," << REG_GENP_2 << "," << tac->getResult() << "\n" <<
			"\t" << "add " << REG_GENP_3 << "," << REG_GENP_3 << "," << REG_BASE << "\n";
		}
		outputFile << "\t" << "sw " << "0(" << REG_GENP_3 << ")," << REG_GENP_1 << "\n";
	} 
	else if(op == TAC_READ) {
		outputFile << "\t" << "add " << REG_GENP_1 << ",R0,R0" << "\t% Scanf\n" <<
		"\t" << "getc " << REG_GENP_1 << "\n";
		saveResult( tac, REG_GENP_1 );
	}
	else if(op == TAC_WRITE) { 
		loadOperand1( tac, REG_GENP_1 );
		outputFile << "\t" << "putc " << REG_GENP_1 << "\n";
	}
	else if(op == TAC_SAVE_RET_ADDR) {
		outputFile << "\t" << "sw "<< ADDR_OLD_RET_ADDR << "(" << REG_BASE << ")," << REG_RET_ADDR << 
		"\t% save return address" << "\n";
	}
	else if(op == TAC_NUM_CONST) {
		outputFile << 
		"\t" << "addi "<< REG_GENP_1 <<",R0," << tac->getOperand1() << 
		"\t% get num const #" << tac->getOperand1() << "\n" <<
		"\t" << "sl " << REG_GENP_1 << ",2\n" <<
		"\t" << "lw " << REG_GENP_2 << ",nums(" << REG_GENP_1 << ")\n" <<
		"\t" << "sw " << tac->getResult() << "(" << REG_BASE << ")," << REG_GENP_2 << "\n";
	}
	else if(op == TAC_CHAR_CONST) {
		outputFile << 
		"\t" << "addi "<< REG_GENP_1 <<",R0," << tac->getOperand1() << 
		"\t% get char const #" << tac->getOperand1() << "\n" <<
		"\t" << "lb " << REG_GENP_2 << ",chars(" << REG_GENP_1 << ")\n" <<
		"\t" << "sw " << tac->getResult() << "(" << REG_BASE << ")," << REG_GENP_2 << "\n";
	}
	else if(op == TAC_ASSIGN) {
		loadOperand1( tac, REG_GENP_1 );
		saveResult( tac, REG_GENP_1 );
	}
	else if(op == TAC_SAVE_OLD_TOPSTK || op == TAC_SET_NEW_TOPSTK) {
		/* DONE IN TAC_CALL */
	}
	else if(op == TAC_NO_OP) {
		outputFile << "\t" << "nop" << "\n";
	}
	else if(op == TAC_IF_FALSE) {
		outputFile << "\t" << "lw " REG_GENP_1 << "," << tac->getOperand1() << "(" << REG_BASE << ")" <<
			"\t% if false" << "\n" <<
			"\t" << "bz " << REG_GENP_1 << "," << tac->getGotoLabel() << "\n";
	}
	else if(op == TAC_GOTO) {
		outputFile << "\t" << "j " << tac->getGotoLabel() << "\t% goto" << "\n";
	}
	else if(op == TAC_RESTORE_RET_ADDR) {
		outputFile << "\t" << "lw "<< REG_RET_ADDR << "," << ADDR_OLD_RET_ADDR << "(" << REG_BASE << ")"
		"\t% restore return address" << "\n";
	}
	else if(op == TAC_RESTORE_OLD_TOPSTK) {
		outputFile << "\t" << "lw " << REG_FSIZE << "," << ADDR_OLD_FSIZE << "(" << REG_BASE << ")" <<
		"\t% restore top of the stack" << "\n" <<
		"\t" << "lw "<<REG_BASE<<","<< ADDR_OLD_BASE << "(" << REG_BASE << ")\n";
	}
	else if(op == TAC_GOTO_RETADDR) {
		outputFile << "\t" << "jr "<< REG_RET_ADDR << "\n";
	}
	else {
		std::cout << "unhandled TAC: " << *tac << "\n";
	}
}

std::string MoonGenerator::getMoonArithmeticOperator(OpCode op) {
	if(!isArithmeticOperator(op)) return "";
	return arithmeticOperators[op-TAC_MULT];
}

bool MoonGenerator::isArithmeticOperator(OpCode op) {
	return (op >= TAC_MULT && op <= TAC_OR);
}

void MoonGenerator::saveResult(const ThreeAddressCode* tac, 
		std::string sourceRegister,	std::string unusedRegister) {
	if(tac->isResultParam()) {
		outputFile << "\t" << "lw " << unusedRegister << "," << tac->getResult() << "(" << REG_BASE ")\n" << 
		"\t" << "sw " << "0(" << unusedRegister << ")," << sourceRegister << "\n";
	} else {
		outputFile << "\t" << "sw " << tac->getResult() << "(" << REG_BASE << ")," << sourceRegister << "\n";
	}
}

void MoonGenerator::loadOperand1(const ThreeAddressCode* tac, std::string targetRegister) {
	outputFile << "\t" << "lw " << targetRegister << "," << tac->getOperand1() << "(" << REG_BASE << ")\n"; 
	if(tac->isOperand1aParam()) {
		outputFile << "\t" << "lw " << targetRegister << ",0(" << targetRegister << ")\n";
	}
}

void MoonGenerator::loadOperand2(const ThreeAddressCode* tac, std::string targetRegister) {
	outputFile << "\t" << "lw " << targetRegister << "," << tac->getOperand2() << "(" << REG_BASE << ")\n"; 
	if(tac->isOperand2aParam()) {
		outputFile << "\t" << "lw " << targetRegister << ",0(" << targetRegister << ")\n";
	}
}
